package parser

import (
	"fmt"
	"os"
	"test/ast"
	"test/lexer"
)

func TestLetStatements() {
	input := `
	let x = 5 + 1;
	let  = 10;
	let 838383;
`
	l := lexer.New(input)
	p := New(l)

	program := p.ParseProgram()

	if program == nil {
		fmt.Fprintf(os.Stderr, "ParseProgram() returned nil\n")
		return
	}

	checkParserErrors(p)

	if len(program.Statements) != 3 {
		fmt.Fprintf(os.Stderr, "Statements lenth error, got %d\n", len(program.Statements))
	}

	for _, statement := range program.Statements {
		if statement.TokenLiteral() != "let" {
			fmt.Fprintf(os.Stderr, "this is not an ast LetStatement, got %s\n", statement.TokenLiteral())
		}
		letStatement, ok := statement.(*ast.LetStatement)
		if !ok {
			fmt.Fprintf(os.Stderr, "this is not an ast LetStatement, got %s\n", statement)
		}
		fmt.Fprintf(os.Stdout, "letStatement: {letStatement.Name.Value= %v,letStatement.Name.TokenLiteral()= %v}\n", letStatement.Name.Value, letStatement.Name.TokenLiteral())
	}
}

func TestRetStatements() {
	input := `
	return 5;
	return 10;
	return 993 322;
`
	l := lexer.New(input)
	p := New(l)
	program := p.ParseProgram()
	checkParserErrors(p)

	for _, stmt := range program.Statements {
		returnStmt, ok := stmt.(*ast.ReturnStatement)
		if !ok {
			fmt.Fprintf(os.Stderr, "this is not a return statement!\n")
			continue
		}
		if returnStmt.TokenLiteral() != "return" {
			fmt.Fprintf(os.Stderr, "the token is not 'return', got '%s'\n", returnStmt.TokenLiteral())
		} else {
			fmt.Fprintf(os.Stdout, "%s token found\n", returnStmt.TokenLiteral())
		}
	}
}

func TestIntegerLiteralExpression() {
	input := `5;`

	l := lexer.New(input)
	p := New(l)
	program := p.ParseProgram()
	checkParserErrors(p)

	stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
	if !ok {
		fmt.Fprintf(os.Stderr, "program.Statements[0] is not expression, got '%s'\n", program.Statements[0])
	}

	intLiter, ok := stmt.Expression.(*ast.IntegerLiteral)
	if !ok {
		fmt.Fprintf(os.Stderr, "stmt.Expression is not Identifier, got '%s'\n", stmt.Expression)
	}
	fmt.Fprintf(os.Stdout, "Identifier{ Value: %d, Token: %s }\n", intLiter.Value, intLiter.TokenLiteral())
}

func TestIdentifierExpression() {
	input := `foobar`

	l := lexer.New(input)
	p := New(l)
	program := p.ParseProgram()
	checkParserErrors(p)

	stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
	if !ok {
		fmt.Fprintf(os.Stderr, "program.Statements[0] is not expression, got '%s'\n", program.Statements[0])
	}

	identifier, ok := stmt.Expression.(*ast.Identifier)
	if !ok {
		fmt.Fprintf(os.Stderr, "stmt.Expression is not Identifier, got '%s'\n", stmt.Expression)
	}

	fmt.Fprintf(os.Stdout, "Identifier{ Value: %s, Token: %s }\n", identifier.Value, identifier.TokenLiteral())
}

func checkParserErrors(p *Parser) {
	errors := p.Errors()
	if len(errors) == 0 {
		return
	}
	fmt.Fprintf(os.Stderr, "%d errors have found!\n", len(errors))
	for _, msg := range errors {
		fmt.Fprintf(os.Stderr, "parser error: %s!\n", msg)
	}

	os.Exit(-1)
}

func TestParsingPrefixExpressions() {

	prefixTests := []struct {
		input        string
		operator     string
		integerValue int64
	}{
		{"!5", "!", 5},
		{"-15", "-", 15},
	}

	for _, tt := range prefixTests {
		l := lexer.New(tt.input)
		p := New(l)
		program := p.ParseProgram()
		checkParserErrors(p)

		if len(program.Statements) != 1 {
			fmt.Fprintf(os.Stderr, "program.Statements count error, get [%d]", len(program.Statements))
		}

		stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
		if !ok {
			fmt.Fprintf(os.Stderr, "program.Statements[0] is not ast.ExpressionStatement, got %T", program.Statements[0])
		}

		exp, ok := stmt.Expression.(*ast.PrefixExpression)
		if !ok {
			fmt.Fprintf(os.Stderr, "stmt is not ast.PrefixExpression, got %T", stmt.Expression)
		}

		if exp.Operator != tt.operator {
			fmt.Fprintf(os.Stderr, "exp.Operator is not %s, got %s", tt.operator, exp.Operator)
		}

		if !testIntegerLiteral(exp.Right, tt.integerValue) {
			return
		}

		fmt.Fprintf(os.Stdout, "%v\n", program)

	}

}

func testIntegerLiteral(il ast.Expression, value int64) bool {
	intger, ok := il.(*ast.IntegerLiteral)
	if !ok {
		fmt.Fprintf(os.Stderr, "il is not ast.IntegerLiteral, got %T", il)
		return false
	}
	if intger.Value != value {
		fmt.Fprintf(os.Stderr, "intger.Value is not %d, got %d", value, intger.Value)
		return false
	}
	if intger.TokenLiteral() != fmt.Sprintf("%d", value) {
		fmt.Fprintf(os.Stderr, "intger.TokenLiteral() is not %d, got %d", value, intger.TokenLiteral())
		return false
	}
	return true
}

func TestParsingInfixExpressions() {
	tests := []string{
		` - a * b`,
		`!-a`,
		`a + b +c`,
		`a+b*c`,
		`a*b/c`,
		`a+b*c-d/e+f`,
		`3+4-5*5`,
		`5>4 == 3<4`,
	}

	for _, test := range tests {
		l := lexer.New(test)
		p := New(l)
		program := p.ParseProgram()
		fmt.Fprintf(os.Stdout, "orig expression is -> %s, parsed as -> %s\n", test, program.String())
	}
}

func testIdentifier(exp ast.Expression, value string) bool {
	ident, ok := exp.(*ast.Identifier)
	if !ok {
		fmt.Fprintf(os.Stderr, "exp is not ast.Identifier, it is %T\n", exp)
		return false
	}

	if ident.Value != value {
		fmt.Fprintf(os.Stderr, "ident.Value is not %s, it is %s\n", value, ident.Value)
		return false
	}

	if ident.TokenLiteral() != value {
		fmt.Fprintf(os.Stderr, "ident.TokenLiteral is not %s, it is %s\n", value, ident.TokenLiteral())
		return false
	}

	return true

}

func testLiteralExpression(expression ast.Expression, expected interface{}) bool {
	switch v := expected.(type) {
	case int:
		return testIntegerLiteral(expression, int64(v))
	case int64:
		return testIntegerLiteral(expression, v)
	case string:
		return testIdentifier(expression, v)
	}
	fmt.Fprintf(os.Stderr, "Unhandled expression type %T\n", expression)
	return false
}

func testInfixExpression(expression ast.Expression, left interface{}, operator string, right interface{}) bool {
	opExp, ok := expression.(*ast.InfixExpression)
	if !ok {
		fmt.Fprintf(os.Stderr, "exp is not ast.InfixExpression, it is %T\n", opExp)
		return false
	}
	if !testLiteralExpression(opExp.Left, left) {
		fmt.Fprintf(os.Stderr, "excepted left expression = %s, got %s\n", left, opExp.Left)
		return false
	}
	if opExp.Operator != operator {
		fmt.Fprintf(os.Stderr, "expected operator = %s, got %s\n", operator, opExp.Operator)
		return false
	}
	if !testLiteralExpression(opExp.Right, right) {
		fmt.Fprintf(os.Stderr, "excepted right expression = %s, got %s\n", right, opExp.Right)
		return false
	}
	return true
}

func TestGeneral(input string) {
	l := lexer.New(input)
	p := New(l)
	program := p.ParseProgram()
	fmt.Fprintf(os.Stdout, program.String())
}

func TestIfExpression() {
	input := `
	if(x<y){x}
`
	l := lexer.New(input)
	p := New(l)
	program := p.ParseProgram()
	fmt.Fprintf(os.Stdout, program.String())
}

func TestFunctionLiteralParsing() {
	input := `
	fn(x,y){
		x+y;
		y + z;
		1 + 1 + 6 - z;
	}
`
	l := lexer.New(input)
	p := New(l)
	program := p.ParseProgram()
	fmt.Fprintf(os.Stdout, program.String())
}
